package org.redmagic.client.spring;

import java.util.HashSet;
import java.util.Properties;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.redmagic.client.RedMagicConfiguration;
import org.redmagic.client.api.Location;
import org.redmagic.spring.RedMagicStringValueResolver;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;

import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.common.collect.Sets.SetView;

public class RedMagicPropertyPlaceholderConfigurer implements BeanFactoryPostProcessor, BeanNameAware, BeanFactoryAware {
	
	private static final Log LOG = LogFactory.getLog(RedMagicPropertyPlaceholderConfigurer.class);
	
	private RedMagicConfiguration redMagic;
	
	private BeanFactory beanFactory;

	private String beanName;
	
	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

		RedMagicBeanDefinitionVisitor visitor = new RedMagicBeanDefinitionVisitor(new RedMagicStringValueResolver());

		// Check the bean definitions to look for placeholders
		String[] beanNames = beanFactory.getBeanDefinitionNames();
		for (String curName : beanNames) {
			// Check that we're not parsing our own bean definition,
			// to avoid failing on unresolvable placeholders in properties file locations.
			if (!(curName.equals(this.beanName) && beanFactory.equals(this.beanFactory))) {
				BeanDefinition bd = beanFactory.getBeanDefinition(curName);
				try {
					visitor.collectBeanDefinition(bd);
				}
				catch (Exception ex) {
					throw new BeanDefinitionStoreException(bd.getResourceDescription(), curName, ex.getMessage());
				}
			}
		}
		
		// Get the placeholders that were found
		ImmutableSet<String> placeholders = ImmutableSet.copyOf(visitor.getPlaceholders());
		
		// Resolve the placeholders into properties
		Properties resolvedProperties = resolvePlaceholders(placeholders);
		
		// Verify all of the placeholders were able to be resolved
		if (placeholders.size() != resolvedProperties.size()) {
			SetView<String> unresolvedPlaceholders = Sets.difference(placeholders, resolvedProperties.keySet());
			for (String unresolvedPlaceholder : unresolvedPlaceholders) {
				LOG.error("Unable to resolve placeholder: " + unresolvedPlaceholder);
			}
			
			throw new RuntimeException("Unable to resolve " + unresolvedPlaceholders.size() + " placeholders");
		}
		
		// Finished with redmagic, shut down each of the locations (and save the cache if enabled)
		redMagic.finished(resolvedProperties);
		
		// Update the placeholders
		visitor.resolvePlaceholders(resolvedProperties);
	}
	
	private Properties resolvePlaceholders(ImmutableSet<String> placeholders) {
		
		Set<String> unresolvedPlaceholders = new HashSet<String>(placeholders);
		Properties resolvedPlaceholders = new Properties();

		// Loop until we find all of the properties to update or we run out of locations to check
		while (!unresolvedPlaceholders.isEmpty()) {
			
			Location location = redMagic.getNextLocation();
			
			try {
				
				if (location == null) {
					break;
				}
				
				// Go to the location to resolve the properties
				Properties results = location.resolvePlaceholders(unresolvedPlaceholders);
				
				// Merge all of the resolved properties into a single Properties object
				resolvedPlaceholders.putAll(results);
				
				// Stop looking for the properties that we found
				for (Object key : results.keySet()) {
					unresolvedPlaceholders.remove(key);
				}
				
			} catch (Exception e) {
				LOG.error("Error during property resolution from location: " + location, e);
			}
		}

		return resolvedPlaceholders;
	}
	
	/**
	 * Only necessary to check that we're not parsing our own bean definition,
	 * to avoid failing on unresolvable placeholders in properties file locations.
	 * The latter case can happen with placeholders for system properties in
	 * resource locations.
	 * @see #setLocations
	 * @see org.springframework.core.io.ResourceEditor
	 */
	public void setBeanName(String beanName) {
		this.beanName = beanName;
	}

	/**
	 * Only necessary to check that we're not parsing our own bean definition,
	 * to avoid failing on unresolvable placeholders in properties file locations.
	 * The latter case can happen with placeholders for system properties in
	 * resource locations.
	 * @see #setLocations
	 * @see org.springframework.core.io.ResourceEditor
	 */
	public void setBeanFactory(BeanFactory beanFactory) {
		this.beanFactory = beanFactory;
	}
	
	public void setRedMagicConfiguration(RedMagicConfiguration redMagicConfiguration) {
		this.redMagic = redMagicConfiguration;
	}
}
